package com.yami.shop.service;

import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Set;

/**
 * @author DELL
 */
@Service
@RequiredArgsConstructor
public class SimilarityService {
    @Resource
    private  RedisTemplate<String, String> redisTemplate;

    /**
     * 计算两个用户之间的Jaccard(Jaccard指数，又称为Jaccard相似系数)相似度
     *
     * @param userId1 第一个用户的唯一标识符
     * @param userId2 第二个用户的唯一标识符
     * @return 两个用户的Jaccard相似度值，范围[0,1]
     *         当无有效数据时返回0.0
     */
    public double calculateJaccardSimilarity(Long userId1, Long userId2) {
        // 构造Redis中存储用户数据的键名
        String userKey1 = "user:" + userId1 + ":items";
        String userKey2 = "user:" + userId2 + ":items";

        // 通过Redis集合操作计算交集和并集
        // 使用临时键存储中间结果，避免污染原始数据
        Long intersection = redisTemplate.opsForSet().intersectAndStore(userKey1, userKey2, "temp:intersection");
        Long union = redisTemplate.opsForSet().unionAndStore(userKey1, userKey2, "temp:union");

        // 清理临时存储的中间结果
        redisTemplate.delete("temp:intersection");
        redisTemplate.delete("temp:union");

        // 处理空数据集情况，防止除以零错误
        return union == 0 ? 0 : (double) intersection / union;
    }

    /**
     * 缓存目标用户的Top-N相似用户列表
     *
     * @param targetUserId 需要计算相似度的目标用户ID
     * @param topN 需要保留的最高相似度用户数量
     *             会更新Redis有序集合保留指定排名范围
     */
    public void cacheUserSimilarities(Long targetUserId, int topN) {
        // 获取目标用户的Redis键
        String targetUserKey = "user:" + targetUserId + ":items";

        // 获取系统中所有用户的物品集合键
        Set<String> allUserKeys = redisTemplate.keys("user:*:items");

        // 处理每个候选用户
        allUserKeys.stream()
                .map(key -> Long.parseLong(key.split(":")[1]))  // 从键名中提取用户ID
                .filter(otherUserId -> !otherUserId.equals(targetUserId))
                .forEach(otherUserId -> {
                    // 计算并存储相似度得分
                    double similarity = calculateJaccardSimilarity(targetUserId, otherUserId);
                    if (similarity > 0) {
                        String similarityKey = "similarity:user:" + targetUserId;
                        redisTemplate.opsForZSet().add(similarityKey, otherUserId.toString(), similarity);
                    }
                });

        // 维护有序集合，仅保留最高相似度的topN个用户
        String similarityKey = "similarity:user:" + targetUserId;
        redisTemplate.opsForZSet().removeRange(similarityKey, 0, -topN - 1);
    }
}
